import 'dart:async';

import 'package:serverpod/serverpod.dart';

import '../../../../../core.dart';
import '../util/default_code_generators.dart';
import '../util/registration_password_policy.dart';
import 'email_idp.dart';

/// Function to be called after a password reset is successfully completed.
typedef OnPasswordResetCompletedFunction =
    void Function(
      Session session, {
      required UuidValue emailAccountId,
      required Transaction? transaction,
    });

/// Function to be called to check whether a password matches the requirements during registration.
typedef PasswordValidationFunction =
    bool Function(
      String password,
    );

/// Function for sending out the verification codes for password reset requests.
typedef SendPasswordResetVerificationCodeFunction =
    void Function(
      Session session, {
      required String email,
      required UuidValue passwordResetRequestId,

      /// The code that was generated by [passwordResetCodeGenerator] to verify this registration request.
      required String verificationCode,
      required Transaction? transaction,
    });

/// Function for sending out the verification codes to verify the email ownership during the initial registration.
typedef SendRegistrationVerificationCodeFunction =
    void Function(
      Session session, {
      required String email,
      required UuidValue accountRequestId,

      /// The code that was generated by [registrationVerificationCodeGenerator] to verify this registration request.
      required String verificationCode,
      required Transaction? transaction,
    });

/// Function to be called after a new email account has been created.
typedef AfterAccountCreatedFunction =
    FutureOr<void> Function(
      Session session, {
      required String email,
      required UuidValue authUserId,
      required UuidValue emailAccountId,
      required Transaction? transaction,
    });

/// {@template email_idp_config}
/// Configuration options for the email account module.
/// {@endtemplate}
class EmailIdpConfig extends IdentityProviderBuilder<EmailIdp> {
  /// The pepper used for hashing passwords and verification codes.
  ///
  /// To rotate peppers without invalidating existing passwords, use [fallbackSecretHashPeppers].
  final String secretHashPepper;

  /// Optional fallback peppers for validating passwords and verification codes created with previous peppers.
  ///
  /// When rotating peppers, add old peppers to this list to allow existing passwords
  /// to continue working. The system will try [secretHashPepper] first, then
  /// each fallback pepper in order until a match is found.
  ///
  /// This is optional and defaults to an empty list.
  final List<String> fallbackSecretHashPeppers;

  /// The time for the registration email verification code to be valid.
  ///
  ///  Default is 15 minutes.
  final Duration registrationVerificationCodeLifetime;

  /// Function returning the registration verification code.
  ///
  /// By default this is a 8 digits alpha-numeric lower-case code.
  final String Function() registrationVerificationCodeGenerator;

  /// How many attempts are permitted for an email verification code (during the [registrationVerificationCodeLifetime] window).
  ///
  /// Defaults to 3.
  final int registrationVerificationCodeAllowedAttempts;

  /// The time for password reset verification codes to be valid.
  ///
  ///  Default is 15 minutes.
  final Duration passwordResetVerificationCodeLifetime;

  /// How many attempts are permitted for a single password reset verification code (during the [passwordResetVerificationCodeLifetime] window).
  ///
  /// Defaults to 3.
  final int passwordResetVerificationCodeAllowedAttempts;

  /// Function returning the password rest verification code.
  ///
  /// By default this is a 8 digits alpha-numeric lower-case code.
  final String Function() passwordResetVerificationCodeGenerator;

  /// The maximum number of failed logins for the same email.
  ///
  /// Successful logins are not counted or limited.
  ///
  /// Defaults to allowing up to 5 request for a sliding 5 minute.
  /// Beyond that, login request will be rejected without checking the credentials.
  final RateLimit failedLoginRateLimit;

  /// Callback to be invoked for sending outgoing registration emails for the email address verification.
  final SendRegistrationVerificationCodeFunction?
  sendRegistrationVerificationCode;

  /// Callback to be invoked for sending outgoing password reset emails.
  final SendPasswordResetVerificationCodeFunction?
  sendPasswordResetVerificationCode;

  /// Callback to be invoked after a password reset is successfully completed.
  ///
  /// This can be used to perform additional cleanup tasks, such as clearing legacy passwords
  /// during migration scenarios.
  final OnPasswordResetCompletedFunction? onPasswordResetCompleted;

  /// Callback to be invoked after a new email account has been created.
  ///
  /// This can be used to perform additional setup tasks, such as creating a
  /// user profile or sending a welcome email.
  final AfterAccountCreatedFunction? onAfterAccountCreated;

  /// Function to check passwords against a policy during registration and password change.
  ///
  /// If the rules are changed after a password has been set, subsequent logins with
  /// the old password are still accepted and the user will not be forced to update it.
  ///
  /// Defaults to ensuring that the password is not padded by whitespace and is at least 8 characters long.
  final PasswordValidationFunction passwordValidationFunction;

  /// How many password reset attempts are allowed for the same email.
  ///
  /// Defaults to allowing at most 3 attempts in the last hour.
  final RateLimit maxPasswordResetAttempts;

  /// The length of the random hash in bytes to be used for each password.
  ///
  /// Defaults to 16.
  final int secretHashSaltLength;

  /// Create a new email account configuration.
  const EmailIdpConfig({
    required this.secretHashPepper,
    this.fallbackSecretHashPeppers = const [],
    this.registrationVerificationCodeLifetime = const Duration(minutes: 15),
    this.registrationVerificationCodeAllowedAttempts = 3,
    this.registrationVerificationCodeGenerator =
        defaultVerificationCodeGenerator,
    this.passwordResetVerificationCodeLifetime = const Duration(minutes: 15),
    this.passwordResetVerificationCodeAllowedAttempts = 3,
    this.passwordResetVerificationCodeGenerator =
        defaultVerificationCodeGenerator,
    this.sendRegistrationVerificationCode,
    this.sendPasswordResetVerificationCode,
    this.onPasswordResetCompleted,
    this.failedLoginRateLimit = const RateLimit(
      maxAttempts: 5,
      timeframe: Duration(minutes: 5),
    ),
    this.passwordValidationFunction =
        defaultRegistrationPasswordValidationFunction,
    this.maxPasswordResetAttempts = const RateLimit(
      timeframe: Duration(hours: 1),
      maxAttempts: 3,
    ),
    this.secretHashSaltLength = 16,
    this.onAfterAccountCreated,
  });

  @override
  EmailIdp build({
    required final TokenManager tokenManager,
    required final AuthUsers authUsers,
    required final UserProfiles userProfiles,
  }) {
    return EmailIdp(
      this,
      tokenManager: tokenManager,
      authUsers: authUsers,
      userProfiles: userProfiles,
    );
  }
}

/// Creates a new [EmailIdpConfig] instance from keys on the `passwords.yaml` file.
///
/// This constructor requires that a [Serverpod] instance has already been initialized.
class EmailIdpConfigFromPasswords extends EmailIdpConfig {
  /// Creates a new [EmailIdpConfigFromPasswords] instance.
  EmailIdpConfigFromPasswords({
    super.fallbackSecretHashPeppers,
    super.registrationVerificationCodeLifetime,
    super.registrationVerificationCodeAllowedAttempts,
    super.registrationVerificationCodeGenerator,
    super.passwordResetVerificationCodeLifetime,
    super.passwordResetVerificationCodeAllowedAttempts,
    super.passwordResetVerificationCodeGenerator,
    super.sendRegistrationVerificationCode,
    super.sendPasswordResetVerificationCode,
    super.onPasswordResetCompleted,
    super.failedLoginRateLimit,
    super.passwordValidationFunction,
    super.maxPasswordResetAttempts,
    super.secretHashSaltLength,
    super.onAfterAccountCreated,
  }) : super(
         secretHashPepper: Serverpod.instance.getPassword(
           'emailSecretHashPepper',
         )!,
       );
}

/// A rolling rate limit which allows [maxAttempts] in the most recent [timeframe].
class RateLimit {
  /// The maximum number of attempts allowed within the timeframe.
  final int maxAttempts;

  /// The timeframe within which the attempts are allowed.
  final Duration timeframe;

  /// Creates a new [RateLimit] instance.
  const RateLimit({required this.maxAttempts, required this.timeframe});
}
